﻿Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.ComponentModel.Design.Serialization
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.Workflow.ComponentModel.Compiler
Imports System.Workflow.ComponentModel.Design
Imports System.Workflow.ComponentModel.Serialization

Namespace System.Workflow.ComponentModel
    <DesignerSerializer(GetType(BindMarkupExtensionSerializer), GetType(WorkflowMarkupSerializer)), TypeConverter(GetType(ActivityBindTypeConverter)), Browsable(True), ActivityValidator(GetType(ActivityBindValidator))> _
    Public NotInheritable Class ActivityBind
        Inherits MarkupExtension
        Implements IPropertyValueProvider
        ' Methods
        Public Sub New()
            Me.designMode = True
            Me.syncRoot = New Object
            Me.id = String.Empty
            Me.path = String.Empty
        End Sub

        Public Sub New(ByVal name As String)
            Me.designMode = True
            Me.syncRoot = New Object
            Me.id = String.Empty
            Me.path = String.Empty
            Me.id = name
        End Sub

        Public Sub New(ByVal name As String, ByVal path As String)
            Me.designMode = True
            Me.syncRoot = New Object
            Me.id = String.Empty
            Me.path = String.Empty
            Me.id = name
            Me.path = path
        End Sub

        Private Shared Function GetContextBind(ByVal activityBind As ActivityBind, ByVal activity As Activity, <Out> ByRef contextActivity As Activity) As ActivityBind
            If (activityBind Is Nothing) Then
                Throw New ArgumentNullException("activityBind")
            End If
            If (activity Is Nothing) Then
                Throw New ArgumentNullException("activity")
            End If
            Dim context As New BindRecursionContext
            Dim bind As ActivityBind = activityBind
            contextActivity = activity
            Do While (Not bind Is Nothing)
                Dim activity2 As Activity = Helpers.ParseActivityForBind(contextActivity, bind.Name)
                If (activity2 Is Nothing) Then
                    Return Nothing
                End If
                Dim dataContext As Object = activity2
                Dim memberInfo As MemberInfo = ActivityBind.GetMemberInfo(dataContext.GetType, bind.Path, Nothing)
                If (memberInfo Is Nothing) Then
                    contextActivity = activity2
                    Return bind
                End If
                If TypeOf memberInfo Is FieldInfo Then
                    contextActivity = activity2
                    Return bind
                End If
                If ((Not TypeOf memberInfo Is PropertyInfo OrElse (Not [TryCast](memberInfo,PropertyInfo).PropertyType Is GetType(ActivityBind))) OrElse (dataContext Is Nothing)) Then
                    Return Nothing
                End If
                Dim obj3 As Object = MemberBind.GetValue(memberInfo, dataContext, bind.Path)
                If Not TypeOf obj3 Is ActivityBind Then
                    Return Nothing
                End If
                If context.Contains(contextActivity, bind) Then
                    Return Nothing
                End If
                context.Add(contextActivity, bind)
                contextActivity = activity2
                bind = [TryCast](obj3,ActivityBind)
            Loop
            Return bind
        End Function

        Friend Shared Function GetDataSourceObject(ByVal activity As Activity, ByVal inputName As String, <Out> ByRef name As String) As Object
            If (activity Is Nothing) Then
                Throw New ArgumentNullException("activity")
            End If
            If String.IsNullOrEmpty(inputName) Then
                Throw New ArgumentNullException("inputName")
            End If
            Return Helpers.GetDataSourceActivity(activity, inputName, name)
        End Function

        Friend Shared Function GetMemberInfo(ByVal dataSourceType As Type, ByVal path As String, ByVal targetType As Type) As MemberInfo
            Dim memberInfo As MemberInfo = MemberBind.GetMemberInfo(dataSourceType, path)
            If (((targetType Is Nothing) OrElse Not GetType(Delegate).IsAssignableFrom(targetType)) OrElse ((Not memberInfo Is Nothing) AndAlso TypeOf memberInfo Is EventInfo)) Then
                Return memberInfo
            End If
            Dim method As MethodInfo = targetType.GetMethod("Invoke")
            Dim list As New List([Of] Type)
            Dim info3 As ParameterInfo
            For Each info3 In method.GetParameters
                list.Add(info3.ParameterType)
            Next
            Return dataSourceType.GetMethod(path, (BindingFlags.FlattenHierarchy Or (BindingFlags.Public Or (BindingFlags.Static Or BindingFlags.Instance))), Nothing, list.ToArray, Nothing)
        End Function

        Private Shared Function GetMemberValue(ByVal dataSourceObject As Object, ByVal memberInfo As MemberInfo, ByVal path As String, ByVal targetType As Type) As Object
            If ((TypeOf memberInfo Is FieldInfo OrElse TypeOf memberInfo Is PropertyInfo) OrElse TypeOf memberInfo Is EventInfo) Then
                Return MemberBind.GetValue(memberInfo, dataSourceObject, path)
            End If
            If ((targetType Is Nothing) OrElse Not TypeOf memberInfo Is MethodInfo) Then
                Throw New InvalidOperationException(SR.GetString("Error_MemberNotFound"))
            End If
            Return Delegate.CreateDelegate(targetType, dataSourceObject, DirectCast(memberInfo, MethodInfo))
        End Function

        Friend Shared Function GetRelativePathExpression(ByVal parentActivity As Activity, ByVal childActivity As Activity) As String
            If (Helpers.GetRootActivity(childActivity) Is childActivity) Then
                Return "/Self"
            End If
            Return parentActivity.QualifiedName
        End Function

        Public Function GetRuntimeValue(ByVal activity As Activity) As Object
            If (activity Is Nothing) Then
                Throw New ArgumentNullException("activity")
            End If
            Return Me.InternalGetRuntimeValue(activity, Nothing)
        End Function

        Public Function GetRuntimeValue(ByVal activity As Activity, ByVal targetType As Type) As Object
            If (activity Is Nothing) Then
                Throw New ArgumentNullException("activity")
            End If
            If (targetType Is Nothing) Then
                Throw New ArgumentNullException("targetType")
            End If
            Return Me.InternalGetRuntimeValue(activity, targetType)
        End Function

        Private Function InternalGetRuntimeValue(ByVal activity As Activity, ByVal targetType As Type) As Object
            Dim runtimeValue As Object = Nothing
            Dim dataSourceObject As Activity = Helpers.ParseActivityForBind(activity, Me.Name)
            If (Not dataSourceObject Is Nothing) Then
                Dim memberInfo As MemberInfo = ActivityBind.GetMemberInfo(dataSourceObject.GetType, Me.Path, targetType)
                If (Not memberInfo Is Nothing) Then
                    runtimeValue = ActivityBind.GetMemberValue(dataSourceObject, memberInfo, Me.Path, targetType)
                    If (TypeOf runtimeValue Is ActivityBind AndAlso (Not BindHelpers.GetMemberType(memberInfo) Is GetType(ActivityBind))) Then
                        runtimeValue = DirectCast(runtimeValue, ActivityBind).GetRuntimeValue(dataSourceObject, targetType)
                    End If
                    Return runtimeValue
                End If
                DependencyProperty.FromName(Me.Path, Helpers.GetRootActivity(activity).GetType)
            End If
            Return runtimeValue
        End Function

        Private Sub OnRuntimeInitialized(ByVal activity As Activity)
            Dim contextActivity As Activity = Nothing
            Dim bind As ActivityBind = ActivityBind.GetContextBind(Me, activity, contextActivity)
            If ((Not bind Is Nothing) AndAlso (Not contextActivity Is Nothing)) Then
                Dim dataSourceType As Type = contextActivity.GetType
                If (Not dataSourceType Is Nothing) Then
                    Dim info As MemberInfo = ActivityBind.GetMemberInfo(dataSourceType, bind.Path, Nothing)
                    If ((Not info Is Nothing) AndAlso ((TypeOf info Is FieldInfo OrElse TypeOf info Is PropertyInfo) OrElse TypeOf info Is EventInfo)) Then
                        If (bind.UserData.Item(UserDataKeys.BindDataSource) Is Nothing) Then
                            bind.UserData.Item(UserDataKeys.BindDataSource) = New Hashtable
                        End If
                        DirectCast(bind.UserData.Item(UserDataKeys.BindDataSource), Hashtable).Item(activity.QualifiedName) = info
                        If (Not contextActivity Is Nothing) Then
                            If (bind.UserData.Item(UserDataKeys.BindDataContextActivity) Is Nothing) Then
                                bind.UserData.Item(UserDataKeys.BindDataContextActivity) = New Hashtable
                            End If
                            DirectCast(bind.UserData.Item(UserDataKeys.BindDataContextActivity), Hashtable).Item(activity.QualifiedName) = contextActivity.QualifiedName
                        End If
                    End If
                End If
            End If
        End Sub

        Public Overrides Function ProvideValue(ByVal provider As IServiceProvider) As Object
            Return Me
        End Function

        Friend Sub SetContext(ByVal activity As Activity)
            Me.designMode = False
            Me.OnRuntimeInitialized(activity)
        End Sub

        Public Sub SetRuntimeValue(ByVal activity As Activity, ByVal value As Object)
            If (activity Is Nothing) Then
                Throw New ArgumentNullException("activity")
            End If
            Dim dataSourceObject As Activity = Helpers.ParseActivityForBind(activity, Me.Name)
            If (Not dataSourceObject Is Nothing) Then
                Dim memberInfo As MemberInfo = ActivityBind.GetMemberInfo(dataSourceObject.GetType, Me.Path, Nothing)
                If (Not memberInfo Is Nothing) Then
                    Dim bind As ActivityBind = [TryCast](ActivityBind.GetMemberValue(dataSourceObject, memberInfo, Me.Path, Nothing),ActivityBind)
                    If (Not bind Is Nothing) Then
                        bind.SetRuntimeValue(dataSourceObject, value)
                    Else
                        MemberBind.SetValue(dataSourceObject, Me.Path, value)
                    End If
                End If
            End If
        End Sub

        Private Function System.Workflow.ComponentModel.Design.IPropertyValueProvider.GetPropertyValues(ByVal context As ITypeDescriptorContext) As ICollection Implements IPropertyValueProvider.GetPropertyValues
            Dim list As New ArrayList
            If ((String.Equals(context.PropertyDescriptor.Name, "Path", StringComparison.Ordinal) AndAlso Not String.IsNullOrEmpty(Me.Name)) AndAlso TypeOf context.PropertyDescriptor Is ActivityBindPathPropertyDescriptor) Then
                Dim outerPropertyContext As ITypeDescriptorContext = DirectCast(context.PropertyDescriptor, ActivityBindPathPropertyDescriptor).OuterPropertyContext
                If (outerPropertyContext Is Nothing) Then
                    Return list
                End If
                Dim component As Activity = [TryCast](PropertyDescriptorUtils.GetComponent(outerPropertyContext),Activity)
                If (component Is Nothing) Then
                    Return list
                End If
                Dim activity2 As Activity = Helpers.ParseActivityForBind(component, Me.Name)
                If (activity2 Is Nothing) Then
                    Return list
                End If
                Dim info As MemberInfo
                For Each info In ActivityBindPropertyDescriptor.GetBindableMembers(activity2, outerPropertyContext)
                    list.Add(info.Name)
                Next
            End If
            Return list
        End Function

        Public Overrides Function ToString() As String
            Dim context As Activity = [TryCast](Me.UserData.Item(UserDataKeys.BindDataContextActivity),Activity)
            If (context Is Nothing) Then
                Return MyBase.ToString
            End If
            Dim qualifiedName As String = String.Empty
            If Not String.IsNullOrEmpty(Me.Name) Then
                qualifiedName = Helpers.ParseActivityForBind(context, Me.Name).QualifiedName
            End If
            If Not String.IsNullOrEmpty(Me.Path) Then
                Dim path As String = Me.Path
                Dim length As Integer = path.IndexOfAny(New Char() { "."c, "/"c, "["c })
                path = If((length <> -1), path.Substring(0, length), path)
                qualifiedName = (qualifiedName & If(Not String.IsNullOrEmpty(qualifiedName), ("." & path), path))
            End If
            Return qualifiedName
        End Function


        ' Properties
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(False)> _
        Private ReadOnly Property DesignMode As Boolean
            Get
                Return (Me.designMode AndAlso Not Me.dynamicUpdateMode)
            End Get
        End Property

        Friend Property DynamicUpdateMode As Boolean
            Get
                Return Me.dynamicUpdateMode
            End Get
            Set(ByVal value As Boolean)
                Me.dynamicUpdateMode = False
            End Set
        End Property

        <DefaultValue(""), SRDescription("ActivityBindIDDescription"), ConstructorArgument("name")> _
        Public Property Name As String
            Get
                Return Me.id
            End Get
            Set(ByVal value As String)
                If Not Me.DesignMode Then
                    Throw New InvalidOperationException(SR.GetString("Error_CanNotChangeAtRuntime"))
                End If
                Me.id = value
            End Set
        End Property

        <DefaultValue(""), SRDescription("ActivityBindPathDescription"), TypeConverter(GetType(ActivityBindPathTypeConverter))> _
        Public Property Path As String
            Get
                Return Me.path
            End Get
            Set(ByVal value As String)
                If Not Me.DesignMode Then
                    Throw New InvalidOperationException(SR.GetString("Error_CanNotChangeAtRuntime"))
                End If
                Me.path = value
            End Set
        End Property

        <Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
        Public ReadOnly Property UserData As IDictionary
            Get
                If (Me.userData Is Nothing) Then
                    SyncLock Me.syncRoot
                        If (Me.userData Is Nothing) Then
                            Me.userData = Hashtable.Synchronized(New Hashtable)
                        End If
                    End SyncLock
                End If
                Return Me.userData
            End Get
        End Property


        ' Fields
        <NonSerialized> _
        Private designMode As Boolean
        <NonSerialized> _
        Private dynamicUpdateMode As Boolean
        Private id As String
        Private path As String
        <NonSerialized> _
        Private syncRoot As Object
        <NonSerialized> _
        Private userData As IDictionary
    End Class
End Namespace

